package coderbyte;

import com.google.common.collect.Collections2;
import java.math.BigInteger;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * solved coderbyte challenges with medium difficulty, each challenge has its
 * own method
 *
 * @author ascorbin
 */
public class Medium {

    /**
     * looks for correctly matched and accounted brackets
     *
     * @param input sequence of characters
     * @return if brackets match 1 and number of accounted brackets, otherwise 0
     */
    String MultipleBrackets(String input) {
        String output = "1";
        int brktUpCnt = 0;
        int brktCnt = 0;
        for (int i = 0; i < input.length(); i++) {
            switch (input.charAt(i)) {
                case '(':
                    brktUpCnt += 1;
                    brktCnt += 1;
                    break;
                case ')':
                    brktCnt -= 1;
                    break;
                case '[':
                    brktUpCnt += 1;
                    brktCnt += 1;
                    break;
                case ']':
                    brktCnt -= 1;
                    break;
            }
            if (brktCnt < 0) {
                output = "0";
                break;
            }
        }
        if (brktCnt == 0 && brktUpCnt > 0) {
            output = "1 " + brktUpCnt;
        } else if (brktCnt > 0) {
            output = "0";
        }

        return output;
    }

    /**
     * checks if parameter is part of the Fibonacci sequence
     *
     * @param num positive integer
     * @return true if param is part of Fibonacci sequence otherwise false
     */
    String FibonacciChecker(int input) {
        String output = "false";
        int intermed = 0;
        int fbn1 = 0;
        int fbn2 = 1;

        while (intermed < input) {
            intermed = fbn1 + fbn2;
            fbn1 = fbn2;
            fbn2 = intermed;
        }

        if (intermed == input) {
            output = "true";
        }

        return output;
    }

    /**
     * specifies the least number of coins, that when added, equal the input
     * parameter
     *
     * @param arg integer ranging from 1 to 250
     * @return least number of coins to build the parameter
     */
    int CoinDeterminer(int arg) {
        int res = 0;
        int cnCnt = 0;
        int[] cnArr = {11, 9, 7, 5, 1};
        //TODO fix errors, algorithm aint working right
        for (int i = cnArr.length - 1; i >= 0; i--) {
            while (arg - res >= cnArr[i]) {
                res += cnArr[i];
                cnCnt += 1;
                System.out.println(cnArr[i]);
            }
        }
        System.out.println(res);
        return cnCnt;
    }

    /**
     * adds up all the multiples of 3 and 5 that are below parameter
     *
     * @param arg a number between 1 and 100
     * @return the sum of all multiples of 3 and 5 below parameter
     */
    int ThreeFiveMultiples(int arg) {
        int res = 0;
        int i = 1;

        while (i * 5 < arg) {
            res += i * 5;
            i++;
        }
        i = 1;
        while (i * 3 < arg) {
            res += i * 3;
            i++;
        }

        return res;
    }

    /**
     * takes two different adjacent characters and replaces them by the third.
     * i.e. ac is reduced to b, ab is reduced to c and bc is reduced to a
     *
     * @param arg sequence of the characters a, b and c
     * @return the smallest length the parameter can be reduced to
     */
    int StringReduction(String arg) {
        StringBuilder res = new StringBuilder().append(arg);
        do {
            if (String.valueOf(res).contains("ab")) {
                res.replace(res.indexOf("ab"), res.indexOf("ab") + 2, "c");
            }
            if (String.valueOf(res).contains("ba")) {
                res.replace(res.indexOf("ba"), res.indexOf("ba") + 2, "c");
            }

            if (String.valueOf(res).contains("ac")) {
                res.replace(res.indexOf("ac"), res.indexOf("ac") + 2, "b");
            }
            if (String.valueOf(res).contains("ca")) {
                res.replace(res.indexOf("ca"), res.indexOf("ca") + 2, "b");
            }

            if (String.valueOf(res).contains("bc")) {
                res.replace(res.indexOf("bc"), res.indexOf("bc") + 2, "a");
            }
            if (String.valueOf(res).contains("cb")) {
                res.replace(res.indexOf("cb"), res.indexOf("cb") + 2, "a");
            }
        } while (String.valueOf(res).contains("ab") || String.valueOf(res).contains("ac")
                || String.valueOf(res).contains("bc") || String.valueOf(res).contains("cb")
                || String.valueOf(res).contains("ba") || String.valueOf(res).contains("ca"));
        return res.length();
    }

    /**
     * looks for correctly matched and accounted brackets
     *
     * @param arg sequence of characters
     * @return 1 if brackets are matched and accounted, otherwise 0
     */
    int BracketMatcher(String arg) {
        int res = 1;
        int brktCnt = 0;
        for (int i = 0; i < arg.length(); i++) {
            if (arg.charAt(i) == '(') {
                brktCnt += 1;
            } else if (arg.charAt(i) == ')') {
                brktCnt -= 1;
            }
            if (brktCnt < 0) {
                res = 0;
                break;
            }
        }
        if (brktCnt > 0) {
            res = 0;
        }

        return res;
    }

    /**
     * finds a straight triple in parameter1 and a straight double in parameter2
     * of the same digits
     *
     * @param arg1 sequence of digits without symbols
     * @param arg2 sequence of digits without symbols
     * @return 1 if a triple and a matching double are found, otherwise 0
     */
    int TripleDouble(Integer arg1, Integer arg2) {
        int res = 0;
        StringBuilder triple = new StringBuilder();
        triple.append("x");
        String doub = String.valueOf(arg2);
        String[] digArr = String.valueOf(arg1).split("");

        for (int i = 0; i < digArr.length; i++) {
            if (i + 2 < digArr.length) {
                if (digArr[i].equals(digArr[i + 1]) && digArr[i].equals(digArr[i + 2])) {
                    triple.setLength(0);
                    triple.append(digArr[i]);
                    triple.append(digArr[i + 1]);
                    if (doub.contains(String.valueOf(triple))) {
                        res = 1;
                    }
                }
            }
        }
        return res;
    }

    /**
     * Searches all the numbers in parameter, adds them up and divides them by
     * the number of letters in parameter. Only single digit numbers are
     * recognized.
     *
     * @param arg sequence of characters, symbols and numbers
     * @return
     */
    String NumberSearch(String arg) {
        int erg = 0;
        int div = 0;

        for (int i = 0; i < arg.length(); i++) {
            if (Character.isDigit(arg.charAt(i))) {
                erg += Character.getNumericValue(arg.charAt(i));
            }
            if (Character.isAlphabetic(arg.charAt(i))) {
                div += 1;
            }
        }

        erg = Math.round((float) erg / div);
        return String.valueOf(erg);
    }

    /**
     * Swaps the case of each character in parameter and if a letter is between
     * two numbers (without separation) the numbers are switched
     *
     * @param arg sequence of characters, symbols and numbers
     * @return
     */
    String SwapII(String arg) {
        StringBuilder res = new StringBuilder();
        boolean sure = false;
        char saveDigit = '\0';

        for (int i = 0; i < arg.length(); i++) {
            if (Character.isUpperCase(arg.charAt(i))) {
                res.append(Character.toLowerCase(arg.charAt(i)));
            } else if (Character.isLowerCase(arg.charAt(i))) {
                res.append(Character.toUpperCase(arg.charAt(i)));
            } else if (Character.isDigit(arg.charAt(i))) {
                if (saveDigit == '\0' && (i + 1 < arg.length())) {
                    for (int j = i + 1; j < arg.length(); j++) {
                        if (Character.isAlphabetic(arg.charAt(j))) {
                            sure = true;
                        } else if (Character.isDigit(arg.charAt(j)) && sure == true) {
                            saveDigit = arg.charAt(i);
                            res.append(arg.charAt(j));
                            break;
                        } else if (!Character.isAlphabetic(arg.charAt(j))) {
                            sure = false;
                            res.append(arg.charAt(i));
                            break;
                        }
                    }
                } else if (saveDigit != '\0') {
                    res.append(saveDigit);
                    saveDigit = '\0';
                } else {
                    res.append(arg.charAt(i));
                }
            } else {
                res.append(arg.charAt(i));
            }
        }
        return String.valueOf(res);
    }

    /**
     * Inserts dashes between each two odd numbers in parameter and inserts
     * asterisks between each two even numbers while zero counts neither as odd
     * nor as even number
     *
     * @param arg sequence of digits
     * @return String
     */
    String DashInsertII(Integer arg) {
        StringBuilder res = new StringBuilder();
        String intStr = String.valueOf(arg);

        for (int i = 0; i < String.valueOf(arg).length() - 1; i++) {
            res.append(intStr.charAt(i));
            if (intStr.charAt(i) % 2 == 1 && intStr.charAt(i + 1) % 2 == 1) {
                res.append("-");
            } else if (intStr.charAt(i) % 2 != 1 && intStr.charAt(i) != '0' && intStr.charAt(i + 1) % 2 != 1 && intStr.charAt(i + 1) != '0') {
                res.append("*");
            }
        }
        res.append(intStr.charAt(String.valueOf(arg).length() - 1));
        return String.valueOf(res);
    }

    /**
     * Checks if any arrangement of arg comes out to be a prime number.
     *
     * @param arg a sequence of digits
     * @return 1 if any arrangement of arg comes out to be a prime number
     * otherwise returns 0
     */
    int PrimeChecker(Integer arg) {
        int res = 0;
        String s = String.valueOf(arg);
        String var = "";
        ArrayList<Integer> intList = new ArrayList<>();
        for (int i = 0; i < String.valueOf(arg).length(); i++) {
            intList.add(Integer.parseInt(String.valueOf(s.charAt(i))));
        }
        Collection<List<Integer>> sortList = Collections2.orderedPermutations(intList);
        for (List<Integer> x : sortList) {
            for (Integer i : x) {
                var = var.concat(String.valueOf(i));
            }
            if (PrimeTime(Integer.parseInt(var))) {
                res = 1;
                break;
            }
            var = "";
        }
        return res;
    }

    /**
     * finds the next number greater than parameter with the same digits
     *
     * @param arg number
     * @return number
     */
    String PermutationStep(Integer arg) {
        String res = "-1";
        String s = String.valueOf(arg);
        String var = "";
        ArrayList<Integer> intList = new ArrayList<>();
        for (int i = 0; i < String.valueOf(arg).length(); i++) {
            intList.add(Integer.parseInt(String.valueOf(s.charAt(i))));
        }
        Collection<List<Integer>> sortList = Collections2.orderedPermutations(intList);
        for (List<Integer> x : sortList) {
            for (Integer i : x) {
                var = var.concat(String.valueOf(i));
            }
            if (Integer.parseInt(var) > arg) {
                res = var;
                break;
            }
            var = "";
        }
        return res;
    }

    /**
     * counts the total number of minutes between the two times in the
     * parameter. this is a duplicate of the easy challenge CountingMinutesI.
     *
     * @param arg time as string properly formatted with a colon and am or pm
     * separated by a hyphen
     * @return string representing minutes
     * @throws ParseException
     */
    String CountingMinutesII(String arg) throws ParseException {
        Easy a = new Easy();
        return a.CountingMinutesI(arg);
    }

    /**
     * divides parameter1 by parameter2 and returns string with properly
     * formatted commas and 4 significant digits after decimal place
     *
     * @param num1 number with positive value
     * @param num2 number with positive value
     * @return properly formatted result
     */
    String FormattedDivision(int num1, int num2) {
        String res;
        double d = (double) num1 / num2;

        NumberFormat nf = NumberFormat.getInstance(Locale.US);
        nf.setMinimumFractionDigits(4);
        res = nf.format(d);

        return res;
    }

    /**
     * finds the number of values to make parameter consecutive
     *
     * @param arg sequence of numbers
     * @return
     */
    int Consecutive(Integer[] arg) {
        int res;
        Integer greatest, lowest;
        List<Integer> intList = new ArrayList<>(Arrays.asList(arg));
        Collections.sort(intList);
        greatest = intList.get(arg.length - 1);
        lowest = intList.get(0);
        res = greatest - lowest;
        res = res - (arg.length - 1);
        return res;
    }

    /**
     * finds the first most repeating number in parameter
     *
     * @param arg sequence of numbers
     * @return first most repeated number
     */
    int SimpleMode(int[] arg) {
        int mode = -1;
        Map<Integer, Integer> arrMap = new LinkedHashMap<>(arg.length);

        for (Integer arg1 : arg) {
            if (arrMap.containsKey(arg1)) {
                arrMap.put(arg1, arrMap.get(arg1) + 1);
            } else {
                arrMap.put(arg1, 1);
            }
        }
        for (Map.Entry<Integer, Integer> entry : arrMap.entrySet()) {
            if (mode < entry.getValue() && entry.getValue() > 1) {
                mode = entry.getKey();
            }
//            System.out.println(String.valueOf(entry.getKey()) + "," + String.valueOf(entry.getValue()));
        }
        return mode;
    }

    /**
     * performs caesar cipher shift on parameter1 with parameter2 as shifting
     * number, numbers won't be shifted
     *
     * @param arg character sequence
     * @param num shifting number
     * @return shifted character sequence
     */
    String CaesarCipher(String arg, int num) {
        StringBuilder res = new StringBuilder();

        for (int i = 0; i < arg.length(); i++) {
            char c = arg.charAt(i);
            if (Character.isAlphabetic(c)) {
                if (Character.isUpperCase(c)) {
                    c = (char) (((int) c + num - (int) 'A') % 26 + (int) 'A');
                } else {
                    c = (char) (((int) c + num - (int) 'a') % 26 + (int) 'a');
                }
            }
            res.append(c);
        }

        return String.valueOf(res);
    }

    /**
     * Returns the first word with the greatest number of repeated letters. This
     * is a duplicate of the easy challenge LetterCountI
     *
     * @param arg sequence of characters as words separated by spaces
     * @return character sequence with greatest number of repeated letters
     */
    String LetterCountII(String arg) {
        Easy a = new Easy();
        String res = a.LetterCountI(arg);
        return res;
    }

    /**
     * converts parameter to decimal value
     *
     * @param arg sequence of 1's and 0's
     * @return decimal value
     */
    int BinaryConverter(String arg) {
        int res = 0;
        arg = new StringBuilder(arg).reverse().toString();
        for (int i = 0; i < arg.length(); i++) {
            if (arg.charAt(i) == '1') {
                res += Math.pow(2, i);
            }
        }
        return res;
    }

    /**
     * finds largest number in parameter and checks if any combination in
     * parameter can be added up to equal the largest number.
     *
     * @param arg non empty, non all the same and maybe negative numbers
     * @return true if the largest number can be build from the other numbers
     */
    boolean ArrayAdditionII(Integer[] arg) {
        boolean res = false;
        List<Integer> arrList = new ArrayList<>(Arrays.asList(arg));
        List<String> binaryList = new ArrayList<>();
        List<Integer> calcList = new ArrayList<>();
        int sum = 0, calcListSum;
        Easy a = new Easy();
        //find largest number and remove it from list
        int largest = Collections.max(arrList);
        arrList.remove(arrList.indexOf(largest));
        for (Integer arrList1 : arrList) {
            sum += arrList1;
        }
        if (sum == largest) {
            res = true;
        } else if (sum > largest) {
            //fill binary list
            for (int i = 1; i < Math.pow(2, arrList.size()) - 1; i++) {
                if (!a.PowersofTwo(i)) {
                    binaryList.add(String.format("%0" + String.valueOf(arrList.size()) + "d", new BigInteger(Integer.toBinaryString(i))));
                }
            }
//            System.out.println(binaryList);
            for (String binaryList1 : binaryList) {
                calcList.clear();
                calcList.addAll(arrList);
                calcListSum = 0;
                //multiply list with binary list entry
                for (int j = 0; j < binaryList1.length(); j++) {
                    calcList.set(j, calcList.get(j) * Character.getNumericValue(binaryList1.charAt(j)));
                }
//                System.out.println(calcList);
                for (Integer calcList1 : calcList) {
                    calcListSum += calcList1;
                }
                if (calcListSum == largest) {
                    res = true;
                    break;
                }
            }
        }
        return res;
    }

    /**
     * checks if parameter follows an arithmetic, geometric or none pattern. is
     * a duplicate from the easy challenges.
     *
     * @param arg sequence of numbers not containing zero
     * @return the name of the evaluated pattern
     */
    String ArithGeoII(int[] arg) {
        int inDif = arg[1] - arg[0];
        int inDiv = arg[1] / arg[0];
        int difCount = 0;
        int divCount = 0;

        for (int i = 0; i < arg.length - 1; i++) {
            if (inDif == (arg[i + 1] - arg[i])) {
                difCount += 1;
            }
            if (inDiv == (arg[i + 1] / arg[i])) {
                divCount += 1;
            }
        }

        if (difCount == arg.length - 1) {
            return "Arithmetic";
        } else if (divCount == arg.length - 1) {
            return "Geometric";
        } else {
            return "-1";
        }
    }

    /**
     * checks if parts of one parameter can be rearranged into the other
     * parameter
     *
     * @param arg1 character sequence without numbers and symbols
     * @param arg2 character sequence without numbers and symbols
     * @return true if one parameter contains the other
     */
    boolean StringScramble(String arg1, String arg2) {
        boolean res = false;
        List<Character> charList1 = new ArrayList<>();
        List<Character> charList2 = new ArrayList<>();

        for (int i = 0; i < arg1.length(); i++) {
            charList1.add(arg1.charAt(i));
        }
        for (int i = 0; i < arg2.length(); i++) {
            charList2.add(arg2.charAt(i));
        }
        if (charList1.size() < charList2.size()) {
            charList2.retainAll(charList1);
            if (charList2.size() == charList1.size()) {
                res = true;
            }
        } else {
            charList1.retainAll(charList2);
            if (charList1.size() == charList2.size()) {
                res = true;
            }
        }

        return res;
    }

    /**
     * finds the greatest common factor of the parameters by using the euclidean
     * algorithm
     *
     * @param arg1 number
     * @param arg2 number
     * @return greatest common factor
     */
    int Division(int arg1, int arg2) {
        int res;

        while (arg2 != 0) {
            res = arg1 % arg2;
            arg1 = arg2;
            arg2 = res;
        }

        return arg1;
    }

    /**
     * checks if parameter is a palindrom irrespective of numbers and symbols
     *
     * @param arg character sequence with numbers and symbols
     * @return true if character sequence is palindrom
     */
    boolean PalindromTwo(String arg) {
//        String clnStr = arg.replaceAll("[-+.:,; !?]", "");
        String clnStr = arg.replaceAll("[^a-zA-Z]", "");
        Easy c = new Easy();

        boolean erg = c.Palindrom(clnStr.toLowerCase());

        return erg;
    }

    /**
     * searches the argth prime number (if parameter is 10 method returns the
     * 10th prime number)
     *
     * @param arg number of the wanted prime number
     * @return the wanted prime number
     */
    int PrimeMover(int arg) {
        int erg;
        int cnt = 0;
        int i = 0;

        do {
            if (PrimeTime(i)) {
                cnt++;
            }
            i++;
        } while (cnt < arg);
        erg = i - 1;

        return erg;
    }

    /**
     * converts parameter using the run-length encoding algorithm
     *
     * @param arg character sequence without numbers, punctuation or symbols
     * @return encoded character sequence
     */
    String RunLength(String arg) {
        StringBuilder erg = new StringBuilder();
        StringBuilder ltr = new StringBuilder();
        String[] charArr = arg.split("");
        ltr.append(charArr[1]);

        for (int i = 1; i < charArr.length; i++) {
            if (i + 1 < charArr.length) {
                if (charArr[i].equals(charArr[i + 1])) {
                    ltr.append(charArr[i + 1]);
                } else {
                    erg.append(ltr.length());
                    erg.append(charArr[i]);
                    ltr.setLength(0);
                    ltr.append(charArr[i + 1]);
                }
            } else {
                erg.append(ltr.length());
                erg.append(charArr[i]);
            }
        }

        return String.valueOf(erg);
    }

    /**
     * checks if parameter is a prime number
     *
     * @param arg number to be checked
     * @return true if paramater is prime
     */
    boolean PrimeTime(int arg) {
        boolean erg = false;

        if (arg == 2) {
            erg = true;
        } else {
            for (int i = 2; i <= arg - 1; i++) {
                if (arg % i == 0) {
                    erg = false;
                    break;
                } else {
                    erg = true;
                }
            }
        }

        return erg;
    }
}
